/*  Copyright (c) 1982 by Manx Software Systems */
/*  Copyright (c) 1982 by Jim Goodnow II        */

/* 80 column Apple II DOS 3.3 Shell version 2013 */
/* Copyright (C) Bill Buckels 2013 */

/*

RD Random Dump Hex Viewer
--

RD(random dump)(C) Copyright Bill Buckels 2013. All Rats Reserved.
Usage: rd options file1 [options] [file2] ... [> myfile.txt]
This program performs a dump in hex and ascii of the specified
file(s) to the standard output. Default is to the screen but can
also be redirected to a file, a printer, or through a filter program.
Options: r p w l o h t - options with values cannot be combined.
+  switch on - options without values can be combined i.e. +rpt
+r switch - view file as random access text file
+p switch - view file a page at a time - Q or ESC to exit
+w(value  - decimal) - display width 1-16 (default 16)
+l(value  - decimal) - record length 1-256 (off by default)
+o(value  - decimal) - view file beginning at offset (default is 0)
+h(value  - hexadecimal) - same as above
+t switch - view file as 7 bit values (strip hi-bits - all files)
-  switch off - options can be combined or separate i.e. -rp or -r -p
Over-rides: +s(slot 4 5 6 7) +d(drive 1 2) +v(volume 1-255)

RD is a command line program for the Aztec C65 DOS 3.3 "Unix-like"
Shell written in Aztec C65 (CII Version).

*/

#define NULL (0)
#include <rwts.h>

int ctrack,csector,sl=6,dr=1,vol=254;
char vbuf[256],cbuf[256],qryname[31],keyname[80],keyvalue=0,qryvalue;

/* turn a string to upper and strip hi-bits */
strupr(str)
char *str;
{
    int i;
    char az;

    for (i = 0; str[i] != 0; i++) {
        az = str[i] & 0x7f;
        if (az > 96 && az < 123) str[i] = (az - 32);
        else str[i] = az;
    }
}

/* current slot,drive, and volume routine - sets defaults */
sdv()
{
    char *ps = 0xaa6a;
    char *pd = 0xaa68;
    char *pv = 0xb3c1;

    sl = (int) ps[0];
    dr = (int) pd[0];
    vol = (int) pv[0];

}

/* set the filename for compare from the catalog sector
   this gives us a c language string */
setqryname(ptr)
char *ptr;
{

  int i;

  /* Copy the file name, stripping the Apple's high bit */

  /* Note: I make no attempt to remove control characters
           if any in the file name. */

  for (i = 0; i < 30; i++)
  {
    qryname[i] = ptr[i] & 0x7f;
  }
  qryname[30] = 0;

  /* remove trailing blanks */
  for (i = 29; i >= 0; i--)
  {
    if (qryname[i] == 0x20) qryname[i] = 0;
    else break;
  }

}



/* filefind walks through the file entries in a catalog sector
   and searches for an active file match.

   if no file match returns the next track in the catalog list.
   if this is 0 then no more catalog sectors.

   if a file match, updates the file type in the catalog sector
   buffer and returns -1;

   the catalog sector buffer is then ready to be written
   back to the catalog.

   */

findfile()
{
    struct catsect *c;
    struct fentry *fe;
    char ch;

    int i,j;

    for (i = 0, j=FINDFIRST; i < CATENTRIES; i++, j+= FINDNEXT) {
       fe = (struct fentry *)&cbuf[j];
       /* deleted file */
       if (fe->track == 0xff) continue;
       /* never used */
       if (fe->track == 0) continue;
       setqryname(fe->name);
       /* if the name matches, update the previous filetype in the
          sector buffer with the new filetype and return a status of -1 */
       if (strcmp(qryname,keyname) == 0) {
           /* save the old file type */
           qryvalue = fe->type;
           /* set the new filetype */
           fe->type = keyvalue;
           /* return without updating the global current
              track and sector values. (ctrack and csector).
              these must remain positioned at the current sector
              for an update to work. otherwise the disk
              will be corrupted... so outa' here rfn!
              */
           return -1;
       }
    }


    c = (struct catsect *)&cbuf[0];

    /* update next catalog track and sector */
    ctrack = (int)c->track;
    csector = (int)c->sector;

    /* return next catalog track, if there are no more
       then this will be 0 */
    return (int)c->track;

}


/* DOS 3.3 file type query */

/* if the update flag is set, qrytype will update the catalog sector
   with the specified type.

   if the update flag is not set, no update will occur.

   in either case the previous type is saved (in qryvalue) each time
   qrytype is called.

   a standard query is used to get the existing filetype.

   an update query is used to change the existing filetype.
*/

qrytype(update)
int update;
{

    struct vtoc *v;
    int status;

    /* get first catalog track and sector from vtoc */
    status = rwts(0x11, 0, &vbuf[0], RWREAD, sl, dr, vol);

    if (status != 0) {
       /* Track $11/Sector $0F (17/15 */
       ctrack = 17;
       csector = 15;
       /*
        printf("VTOC error. rwts returned %d (%x HEX).!\n",status,status);
        return -1;
       */
    }
    else {
        v = (struct vtoc *)&vbuf[0];
        ctrack = (int)v->ctrack;
        csector = (int)v->csector;
    }

    /* read catalog entries and update file type if found */
    for (;;) {
        status = rwts(ctrack, csector, &cbuf[0], RWREAD, sl, dr, vol);
        /* if any problem at all, we are done */
        if (status != 0) {
           /*
           printf("CATALOG error. rwts returned %d (%x HEX).!\n",status,status);
           */
           return -1;
        }

        /* filefind will update ctrack and csector to drive
           the search each time it is called */
        status = findfile();
        if (status == 0) break; /* all done but no match */

        if (status < 0) {
            if (update == 0) return 0;
            /* write the new filetype back to the catalog */
            /* if any problem at all we are done */
            status = rwts(ctrack, csector, &cbuf[0], RWWRITE, sl, dr, vol);
            if (status != 0) {
               /*
               printf("CATALOG error. rwts returned %d (%x HEX)!\n",status,status);
               */
               return -1;
            }
            return 0; /* success! so return silently... */
        }

    }
    /*
    printf("%s not in catalog!\n",keyname);
    */
    return -1;
}

char digit[] = "0123456789ABCDEF";
char buf[512];

/* Copyright (C) Bill Buckels 2013 */
/* get a keypress value directly from the keyboard buffer */
getch()
{
    char *kpress = (char*)0xC000; /* return the last key press  */
    char *kclear = (char*)0xC010; /* clear the last key press   */
    char c;
        /* clear stragglers from the keyboard buffer */
        while((c=kpress[0]) > 127)kclear[0]=0;

        /* read the keyboard buffer */
        /* and return the character */
        do{
           c = kpress[0];
           }while(c < 128);

           c-=128;
           kclear[0]=0;
           return (int )c;
}


char blank[80];
char cr[2] = {13,0};

char *usage[] = {
"RD(random dump)(C) Copyright Bill Buckels 2013. All Rats Reserved.",
"Usage: rd options file1 [options] [file2] ... [> myfile.txt]",
"This program performs a dump in hex and ascii of the specified",
"file(s) to the standard output. Default is to the screen but can",
"also be redirected to a file, a printer, or through a filter program.",
"Options: r p w l o h t - options with values cannot be combined.",
"+  switch on - options without values can be combined i.e. +rpt",
"+r switch - view file as random access text file",
"+p switch - view file a page at a time - Q or ESC to exit",
"+w(value  - decimal) - display width 1-16 (default 16)",
"+l(value  - decimal) - record length 1-256 (off by default)",
"+o(value  - decimal) - view file beginning at offset (default is 0)",
"+h(value  - hexadecimal) - same as above",
"+t switch - view file as 7 bit values (strip hi-bits - all files)",
"-  switch off - options can be combined or separate i.e. -rp or -r -p",
"Over-rides: +s(slot 4 5 6 7) +d(drive 1 2) +v(volume 1-255)",
NULL};

pusage()
{
    int i;

    for (i=0; usage[i] != NULL; i++) {
        puts(usage[i]);
        puts(cr);
    }
}


main(argc, argv)
int argc;
char **argv;
{
    register int infil;
    register unsigned tot;
    register int j, i, k;
    register int c, d, e;
    char str[80];
    register char *cp;
    register unsigned offset;
    char ch,a=0,p=0,r=0,t=0;
    int w=16,l=21,rlen=0, rleft=0, rmode = 0;

    if (argc < 2) {
        pusage();
        exit(1);
    }

    /* get current drive slot and volume */
    sdv();

    /* set static buffers for output strings */
    for (i=0;i<78;i++) {
        blank[i] = 32;
    }
    blank[78] = 13;
    blank[79] = 0;


    offset = 0;
    while (--argc) {
        tot = 0;
        ++argv;
        if (**argv == '+') {
            cp = *argv + 1;
            /* generally needed */
            strupr(cp);
            ch = cp[0];

            if (ch == 'T' || ch == 'R' || ch == 'P') {
                for (i = 0; cp[i]!= 0; i++) {
                    ch = cp[i];
                    if (ch == 0) break;
                    if (ch == 'T') t = 1;
                    if (ch == 'R') r = 1;
                    if (ch == 'P') p = 1;
                }
                continue;
            }
            if (ch == 'H') {
                offset = atoh((char *)&cp[1]);
                offset &= ~7;
                continue;
            }

            /* needed for all option values except hex */
            i = atoi((char *)&cp[1]);

            if (ch == 'W') {
                if (i > 0 && i < 17) w = i;
                continue;
            }
            if (ch == 'L') {
                if (i > 0 || i < 257) {
                    rlen = i;
                    rmode = 1;
                }
                continue;
            }
            if (ch == 'O') {
                offset = i;
                offset &= ~7;
                continue;
            }
            if (i < 1 || i > 255) continue;

            /* not validating further */
            if (ch == 'S' && i < 8)sl = i;
            if (ch == 'D' && i < 3)dr = i;
            if (ch == 'V')vol = i;
            continue;
        }
        /* '-' minus key turns off option
           only used when multiple files are dumped */
        if (**argv == '-') {
            cp = *argv + 1;
            strupr(cp);
            for (i = 0; cp[i]!= 0; i++) {
                ch = cp[i];
                if (ch == 0) break;
                if (ch == 'T') t = 0;
                if (ch == 'R') r = 0;
                if (ch == 'P') p = 0;
                if (ch == 'L') rmode = 0;
                if (ch == 'W') {
                    l = 21;
                    w = 16;
                }
                if (ch == 'O' || ch == 'H') offset = 0;
            }
            continue;
        }

        strcpy(keyname,*argv);
        strupr(keyname);

        a = 0;
        qryvalue = 0xff;
        if (r == 1) {
           keyvalue = 0x08; /* filetype S */
           if (qrytype(1)!=0) {
              puts("Can't change file to type S: ");
              puts(*argv);
              write(1,cr,1);
              continue;
           }
           if ((qryvalue &0x7f) == 0) a = 1;
        }
        else {
           /* 7 bit display test - text files */
           if (qrytype(0) == 0) {
               if ((qryvalue &0x7f) == 0) a = 1;
           }
        }
        if ((infil = open(*argv, 0)) < 0) {
            if (r==1) {
                /* restore original filetype */
                keyvalue = qryvalue;
                qrytype(1);
            }
            puts("Can't open file ");
            puts(*argv);
            write(1,cr,1);
            continue;
        }
        e = 0;
        ch = 0;
        lseek(infil, offset, 0, 0);
        tot = offset;
        rleft = rlen;
        for (;;) {

            /* 2 paging modes - +p must be set */
            if (rmode == 0) {
                /* normal paging mode 23 lines then pause */
                i = read(infil, buf, w);
            }
            else {
                /* record paging mode - pause after record */
                if (rleft > w) {
                    i = read(infil, buf, w);
                    rleft = (rleft - w);
                }
                else {
                    i = read(infil, buf, rleft);
                    rleft = rlen;
                    /* set some high number to pause display
                    after reading each record */
                    e = 911;
                }
            }

            if (i == 0)
                break;
            if (t == 1) {
                for (k=0; k< i; k++) buf[k] &= 0x7f;
            }
            strcpy(str,blank);
            htoa(str, tot);
            for (k=6,j=0;j<i;++j) {
                c = buf[j]&0x0f;
                d = (buf[j] >> 4)&0x0f;
                str[k++] = digit[d];
                str[k++] = digit[c];
                ++k;
            }
            if (a == 1) {
                for (k=0; k< i; k++) buf[k] &= 0x7f;
            }
            for (k=55,j=0;j<i;j++)
                if (buf[j] >= ' ' && buf[j] < 0x7f)
                    str[k++] = buf[j];
                else
                    str[k++] = '.';
            write(1, str, 79);
            tot += i;
            if (p == 1) {
                if (e++ > l) {
                   ch = getch();
                   if (ch == 27 || ch == 'q' || ch == 'Q') break;
                   if (rmode == 1) {
                       write(1,cr,1);
                       e = 1;
                   }
                   else {
                     e=0;
                  }
               }
            }
            else {
                if (e == 911) {
                    write(1,cr,1);
                    e = 0;
                }
            }
        }
        close(infil);
        if (r==1) {
            /* restore original filetype */
            keyvalue = qryvalue;
            qrytype(1);
        }

        if (ch == 27 || ch == 'q' || ch == 'Q') break;
        if (p == 1 && e < (l+1) && e > 0) {
            ch = getch();
        }
        if (ch == 27 || ch == 'q' || ch == 'Q') break;
    }
}

char hx[] = "0123456789ABCDEF";

atoh(cp)
register char *cp;
{
    register unsigned val = 0;
    register int c;

    while (c = index(hx, toupper(*cp++)))
        val = val * 16 + c - hx;
    return(val);
}

htoa(cp, val)
register char *cp;
register unsigned val;
{
    register char *str;

    str = cp + 5;
    while (str > cp) {
        *--str = hx[val&0x0f];
        val >>= 4;
    }
}

puts(str)
char *str;
{
    int i;
    i = strlen(str);
    write(1, str, i);
}
